package org.springframework.beans.factory;

import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.util.StringValueResolver;

import java.io.IOException;
import java.util.Properties;

/**
 * 文件属性占位符的BeanFactoryPostProcessor，修改Bean定义对象的属性，将占位符 修改为实际的数值
 */
public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor {
    //属性占位符的前缀
    public static final String PLACEHOLDER_PREFIX = "${";
    //属性占位符的后缀
    public static final String PLACEHOLDER_SUFFIX = "}";
    //属性文件的路径
    private String location;

    /**
     * bean工厂后处理器增强的执行方法
     *
     * @param beanFactory beanFacory里面存有bean定义对象的map集合
     * @throws BeansException
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        //加载资源配置文件
        Properties properties = loadProperties();
        //将属性值替换掉占位符
        processProperties(beanFactory, properties);

        //往容器中添加字符解析器，供解析@Value注解使用
        PlaceholderResolvingStringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(properties);
        beanFactory.addEmbeddedValueResolver(valueResolver);
    }

    /**
     * 加载属性配置文件
     *
     * @return
     */
    private Properties loadProperties() {
        try {
            //读取属性文件
            DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
            Resource resource = resourceLoader.getResource(location);
            //加载为Properties对象
            Properties properties = new Properties();
            properties.load(resource.getInputStream());
            return properties;
        } catch (IOException e) {
            throw new BeansException("Could not load properties", e);
        }
    }

    /**
     * 将属性值替换掉占位符
     *
     * @param beanFactory
     * @param properties
     * @throws BeansException
     */
    private void processProperties(ConfigurableListableBeanFactory beanFactory, Properties properties) throws BeansException {
        //获取bean定义对象的名称集合
        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
        //遍历bean定义对象的名称
        for (String beanDefinitionName : beanDefinitionNames) {
            //获取每个bean定义对象 分别进行属性值替换占位符的操作
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
            resolvePropertyValues(beanDefinition, properties);
        }
    }

    /**
     * 对每个Bean定义对象进行属性值替换占位符的操作
     *
     * @param beanDefinition
     * @param properties
     */
    private void resolvePropertyValues(BeanDefinition beanDefinition, Properties properties) {
        //获取读取bean定义对象的属性集合 并遍历每个属性
        PropertyValues propertyValues = beanDefinition.getPropertyValues();
        for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
            //获取属性的值
            Object value = propertyValue.getValue();
            //判断是否是字符串
            if (value instanceof String) {
                //解析出占位符真正的值
                value = resolvePlaceholder((String) value, properties);
                //再添加的bean定义对象里即可
                propertyValues.addPropertyValue(new PropertyValue(propertyValue.getName(), value));
            }

        }

    }

    /**
     * 解析占位符的真实值 并且返回
     *
     * @param properties
     * @param value
     * @return
     */
    private String resolvePlaceholder(String value, Properties properties) {
        //TODO 仅简单支持一个占位符的格式
        String strVal = value;
        StringBuffer buf = new StringBuffer(strVal);

        //判断value是否是占位符的格式
        int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);
        int endIndex = strVal.indexOf(PLACEHOLDER_SUFFIX);

        if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) {
            //说明是占位符 截取占位符获取属性的key 替换为实际的数值
            String propKey = strVal.substring(startIndex + 2, endIndex);//截取包左不包右
            String propVal = properties.getProperty(propKey);
            buf.replace(startIndex, endIndex + 1, propVal);//将后面的字符串替换本来的内容 包左不包右
        }

        //返回占位符解析出来的真实值
        return buf.toString();
    }

    /**
     * 内部类 对外提供的用来专门解析占位符的吧
     */
    private class PlaceholderResolvingStringValueResolver implements StringValueResolver {

        private final Properties properties;

        public PlaceholderResolvingStringValueResolver(Properties properties) {
            this.properties = properties;
        }

        @Override
        public String resolveStringValue(String strVal) throws BeansException {
            return PropertyPlaceholderConfigurer.this.resolvePlaceholder(strVal, properties);
        }
    }

}
